c++ 中new与delete的探究以及小问题


返回目录


一、new运算符

自由存储为 type-name 的对象或对象数组分配内存,并将已适当分类的非零指针返回到对象。

1.1、new 的工作方式

包含new运算符的表达式执行三类操作:

  • 定位并保留要分配的对象的存储,此阶段完成后,将分配正确的存储量,但它还不是对象。
  • 初始化对象。 初始化完成后,将为成为对象的已分配存储显示足够的信息。
  • 返回指向派生自 new-type-name 或 type-name的指针类型的对象的指针。 程序使用此指针来访问最近分配的对象。
    (简单来说,就是先分配内存,再构造对象,然后返回指针)

new 运算符调用函数 operator new,为数组以及基本类型对象分配存储
类对象可基于每个类定义其自己的 operator new 静态成员函数。

1.2、关于如何分配内存量

当编译器遇到用于分配类型 type 的对象的 new 运算符时,它将发布对 type::operator new( sizeof( type ) ) 的调用。因此,new 运算符可以为对象分配正确的内存量。

1.3、关于分配失败

如果不成功,则 new 将返回引发异常(std::bad_alloc)
例如下面的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
using namespace std;
#define NUM 500000000
int main() {
int *p = new int[NUM];
if (p == 0x0) {
cout << "内存不足" << endl;
system("pause");
}
else
{
cout << "分配成功" << endl;
system("pause");
}
return 0;
}
```

### 在VS下面,只会返回异常如下图:
![此处输入图片的描述][2]

当调整数据较小时(350000000),可以分配成功.
![此处输入图片的描述][3]

后修改测试代码,
```CPP
for (int i = 0; ;i++)
{
int *p = new int[10000];
if (p == 0x0) {
cout << "内存不足" << endl;
system("pause");
}
}
```
发现,少量多次分配,基本不会出现大问题。程序可以分配到2G内存之后,报出std::bad_alloc异常。

### VC平台
现在换到VC平台,调用分配大块内存,默认为抛出0,而不是异常。
![此处输入图片的描述][4]

## 1.4、解决方法
来自[微软中国][5]
处理失败的内存分配要求的方法:
编写自定义恢复例程来处理此类失败,然后通过调用 _set_new_handler 运行时函数来注册您的函数。


----------


## 二、delete 运算符
### 2.1、用处
用于删除用new申请的空间,释放空间块。
### 2.2、注意点

>- 使用对象的 delete 运算符释放其内存。 在对象中删除后取消引用指针的程序,可能会有不可预知的结果或崩溃。
>- 当 delete用于释放C++类对象的内存时,对象的析构函数会在释放对象内存前调用 (如果对象具有析构函数)。
>- 如果对 delete运算符的操作的是一个可修改的左值,那么在对象删除后其值是未定义的。

---------------------------------


## 三、其他问题
### 3.1在使用deltet后,需要把指针置空
如:
```
delete L.head;
L.head=NULL;
```
那么问题来了:**为什么在delete L.head后,还需要赋值为空,是否多余?**

测试:
```cpp
int main() {
int *p = new int;
*p=5;
delete p;
p=NULL;
}

1、在执行了new之后,返回一个地址给p,
此处输入图片的描述
2、然后,p赋值5;
此处输入图片的描述
3、接下来delete p;值变成未知值。
此处输入图片的描述
但注意!这边并没有把p给释放掉,也就是说,p仍然指向了,一个内存单元,与之前的区别在于,原来那个单元是我自己申请的,属于我管,但是,现在我不想要了, 把他扔掉了,那这块就不归我管了,就好比把房子卖了,保存了钥匙。实际上,程序“应该”已经不具备管理这个内存单元的权利,但是我却没把权利完全给交出去一样.所以,一旦有其他程序,利用了这个内存单元(好比其他人把房子买了过去),而我恰巧操作不当,使用了这个本身不归我管理的“钥匙”,那么就会造成程序的紊乱。(换句话说,就是指向内存块不合法!)

4、因此,我们需要执行第四步,置空。
此处输入图片的描述
这样,就不会产生野指针,造成紊乱。


3.2、悬垂指针

3.2.1定义

当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称悬垂指针。

3.2.2成因

1、 在许多编程语言中,显示地从内存中删除一个对象或者返回时通过销毁栈帧,并不会改变相关的指针的值。该指针仍旧指向内存中相同的位置,即使指针所指的内容已经被删除。一旦误操作该指针,会导致错误。
2、 当一个指针指向的内存被释放后就会变成悬垂指针。可以避免这个问题的一种方法是在释放它的引用后把指针重置为NULL。

3.2.3来自百科的一种解决方法

引入智能指针可以防止垂悬指针出现。一般是把指针封装到一个称之为智能指针类中,这个类中另外还封装了一个使用计数器,对指针的复制等操作将导致该计数器的值加1,对指针的delete操作则会减1,值为0时,指针为NULL


3.3内存泄露

这是与悬垂指针相对立的一个问题。

3.3.1定义

memory leak指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

3.3.2演示

举个简单的例子:
定义一个节点类,然后一次生成5个节点。进行一些操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 struct Node 
{
int a;
Node* next;
};

void main()
{
Node *pHead=new Node;
pHead->a=0;
pHead->next=NULL;
Node *p=pHead;
for (int i=0;i<5;i++)
{
p->next=new Node;
p=p->next;
p->a=i+1;
}
p->next=NULL;
while (pHead)
{
cout<<pHead->a<<endl;
pHead=pHead->next;
}
}

程序理所当然的运行了:
此处输入图片的描述

结果看上去也没错,输出了5个值。
此处输入图片的描述

但是请注意,如果还有后续操作,会发现,根本找不到之前的元素了!
此处输入图片的描述

这就产生了内存泄露,那些元素除非是在程序运行结束,否则空间根本不可能被释放!这是小程序,看不出来,如果在一个大程序中也犯如此低级错,那么程序将消耗巨额内存。